//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
#include "CVideoCaptureGraph.h"

CVideoCaptureGraph::CVideoCaptureGraph( )
{
    // init com
    CoInitialize(NULL);

    m_pCaptureGraphBuilder = NULL;
    m_pGraph = NULL;
    m_pMediaControl = NULL;
    m_pMediaEvent = NULL;
    m_pVideoCapture = NULL;
    m_fInitialized = FALSE;
}

CVideoCaptureGraph::~CVideoCaptureGraph( )
{
    Cleanup();

    // release com
    CoUninitialize();
}

void
CVideoCaptureGraph::Cleanup()
{
    HRESULT hr = S_OK;

    // stop the graph
    if(m_pMediaControl)
        m_pMediaControl->Stop();

    // release all of the core DirectShow components
    // and the video capture filter.
    if(m_pVideoCapture)
    {
        m_pVideoCapture->Release();
        m_pVideoCapture = NULL;
    }

    if(m_pMediaControl)
    {
        m_pMediaControl->Release();
        m_pMediaControl = NULL;
    }

    if(m_pMediaEvent)
    {
        m_pMediaEvent->Release();
        m_pMediaEvent = NULL;
    }

    if(m_pCaptureGraphBuilder)
    {
        m_pCaptureGraphBuilder->Release();
        m_pCaptureGraphBuilder = NULL;
    }

    if(m_pGraph)
    {
        m_pGraph->Release();
        m_pGraph = NULL;
    }

    m_fInitialized = FALSE;
}


HRESULT
CVideoCaptureGraph::BuildGraph(HWND OwnerWnd, RECT *rcCoordinates)
{
    HRESULT hr = S_OK;

    // cleanup just in case BuildGraph is called twice.
    Cleanup();

    // First we configure the core DirectShow components, these
    // are the filtergraph manager, the ICaptureGraphBuilder,
    // IMediaControl, and IMediaEvent
    if(SUCCEEDED(hr = SetupCoreDirectShowComponents(OwnerWnd)))
    {
        // setup the video capture filter to the current camera driver
        if(SUCCEEDED(hr = SetupVideoCaptureFilter()))
        {
            // render the preview pin and set up the preview window
            if(SUCCEEDED(hr = SetupPreview(OwnerWnd, rcCoordinates)))
            {
                // render the still pin, set it's file name
                if(SUCCEEDED(hr = SetupCapture()))
                {
                    //if everything else succeeded, run the graph
                    if(SUCCEEDED(hr = m_pMediaControl->Run()))
                        m_fInitialized = TRUE;
                }
            }
        }
    }

    // if anything above failed, then cleanup the core DirectShow components
    if(FAILED(hr))
        Cleanup();

    return hr;
}

HRESULT
CVideoCaptureGraph::SetupCoreDirectShowComponents(HWND OwnerWnd)
{
    HRESULT hr = S_OK;

    // create the filtergraph manager
    if(SUCCEEDED(hr = CoCreateInstance( CLSID_FilterGraph, NULL, 
                    CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (LPVOID *)&m_pGraph )))
    {
        // create the capture graph builder and add the filtergraph to it.
        if(SUCCEEDED(hr = CoCreateInstance( CLSID_CaptureGraphBuilder, NULL, 
                    CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (LPVOID *)&m_pCaptureGraphBuilder )))
        {
            // now set the capture graph builder to the filtergraph in use
            if(SUCCEEDED(hr = m_pCaptureGraphBuilder->SetFiltergraph( m_pGraph )))
            {
                // query for the IMediaControl interface, this is needed later for controlling the graph
                if(SUCCEEDED(hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl )))
                {
                    // query for the IMediaEvent interface, this is needed later for tracking events
                    if(SUCCEEDED(hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&m_pMediaEvent)))
                        hr = m_pMediaEvent->SetNotifyWindow((OAHWND) OwnerWnd, WM_DSHOWEVENT, 0);
                }
            }
        }
    }

    return hr;
}

HRESULT
CVideoCaptureGraph::SetupVideoCaptureFilter()
{
    HRESULT hr = S_OK;
    IPropertyBag * pPropBag = NULL;
    CComVariant varCamName;
    DEVMGR_DEVICE_INFORMATION di;
    HANDLE handle = INVALID_HANDLE_VALUE;
    IPersistPropertyBag * pVideoPropertyBag = NULL;
    GUID guidCamera = DEVCLASS_CAMERA_GUID;
    VariantInit(&varCamName);

    // initilize the DEVMGR_DEVICE_INFORMATION structure.
    memset(&di, 0, sizeof(DEVMGR_DEVICE_INFORMATION));
    di.dwSize = sizeof(DEVMGR_DEVICE_INFORMATION);

    // search for the cameras in the system which are registered as a DirectShow camera driver.
    // for simplicity use the first one found.
    handle = FindFirstDevice( DeviceSearchByGuid, &guidCamera, &di );
    if(INVALID_HANDLE_VALUE != handle)
    {
        // the camera was found, close the handle and setup the variant
        FindClose( handle );
        varCamName = di.szLegacyName; 
    }

    // if no camera driver was found, we have a failure and cannot continue
    else hr = E_FAIL;
   
    // if we succeeded finding the camera driver, then load the video capture filter
    // and add it to the capture graph.
    if(SUCCEEDED(hr))
    {
        // cocreate and add the video capture filter to the filtergraph
        if(SUCCEEDED(hr = CoCreateInstance( CLSID_VideoCapture, NULL, 
                    CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID *)&m_pVideoCapture )))
        {
            hr = m_pGraph->AddFilter(m_pVideoCapture, TEXT("VideoCapture"));
        }
    }

    // if we succeeded cocreating and adding the video capture filter, configure it to use the camera
    // driver found.
    if(SUCCEEDED(hr))
    {
        // query for the IPropertyBag interface, this is needed to set the camera driver the video capture filter
        // will use
        if(SUCCEEDED(hr = m_pVideoCapture->QueryInterface(IID_IPersistPropertyBag, (void **) &pVideoPropertyBag )))
        {

            if(SUCCEEDED(hr = CoCreateInstance( CLSID_PropertyBag, NULL, 
                    CLSCTX_INPROC_SERVER, IID_IPropertyBag, (LPVOID *)&pPropBag )))
            {
                // write the variant loaded above into the property bag
                if(SUCCEEDED(hr = pPropBag->Write( L"VCapName", &varCamName )))
                {
                    // give the property bag to the video capture filter's IPersistPropertyBag interface
                    hr = pVideoPropertyBag->Load( pPropBag, NULL );
                }
            }
        }
    }

    if(pPropBag)
    {
        pPropBag->Release();
        pPropBag = NULL;
    }

    if(pVideoPropertyBag)
    {
        pVideoPropertyBag->Release();
        pVideoPropertyBag = NULL;
    }

    return hr;
}

HRESULT
CVideoCaptureGraph::SetupPreview(HWND OwnerWnd, RECT *rc)
{
    HRESULT hr = S_OK;
    IBaseFilter *pVideoRenderer = NULL;
    IVideoWindow *pVideoWindow = NULL;
    CMotionFilter *cMotionFilter = NULL;
    IBaseFilter *pMotionFilter = NULL;

    if(SUCCEEDED(hr))
    {
        cMotionFilter = (CMotionFilter *) CMotionFilter::CreateInstance(NULL, &hr);

        if(cMotionFilter)
        {
            hr = cMotionFilter->QueryInterface(IID_IBaseFilter, (void **) &pMotionFilter);
            if(SUCCEEDED(hr) && pMotionFilter)
            {
                if(SUCCEEDED(hr = m_pGraph->AddFilter(pMotionFilter, TEXT("MotionFilter"))))
                {
                    // render the preview pin of the video capture filter. If no preview pin exists
                    // the capture graph builder will insert a smart tee into the graph and tee off of
                    // the capture pin for the preview.
                    if(SUCCEEDED(hr = m_pCaptureGraphBuilder->RenderStream( &PIN_CATEGORY_PREVIEW, 
                                    &MEDIATYPE_Video, m_pVideoCapture, pMotionFilter, NULL )))
                    {
                        // if rendering the preview succeeded, then we need to find the video renderer filter
                        // in order to set up the video window dimensions, location, owner window, etc.
                        if(SUCCEEDED(hr = m_pGraph->FindFilterByName(TEXT("Video Renderer"), &pVideoRenderer)))
                        {
                            if(SUCCEEDED(hr = pVideoRenderer->QueryInterface(IID_IVideoWindow, (void **)&pVideoWindow)))
                            {
                                // for now just set the owner window
                                hr = pVideoWindow->put_Owner((OAHWND) OwnerWnd);
                            }
                        }
                    }
                }
                pMotionFilter->Release();
            }
        }
    }

    if(pVideoWindow)
    {
        pVideoWindow->Release();
        pVideoWindow = NULL;
    }
    if(pVideoRenderer)
    {
        pVideoRenderer->Release();
        pVideoRenderer = NULL;
    }

    return hr;
}

HRESULT
CVideoCaptureGraph::SetupCapture()
{
    HRESULT hr = S_OK;
    IBaseFilter *pAudioCapture = NULL;
    IBaseFilter *pVideoDMOWrapperFilter = NULL;
    IBaseFilter *pMuxFilter = NULL;
    IFileSinkFilter *pVideoFileSink = NULL;
    IDMOWrapperFilter *pVideoEncoderDMO = NULL;
    IPersistPropertyBag *pAudioPropertyBag = NULL;

    // Creating, adding, and configuring the audio capture filter is
    // identical to the video capture filter.
    if(SUCCEEDED(hr = CoCreateInstance( CLSID_AudioCapture, NULL, 
                CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID *)&pAudioCapture )))
    {
        // query for the property bag to set the audio driver to use
        if(SUCCEEDED(hr = pAudioCapture->QueryInterface(IID_IPersistPropertyBag, (void **) &pAudioPropertyBag)))
        {
            // use the default audio driver. If a particular driver was required
            // it is selected the same as the camera driver.
            if(SUCCEEDED(hr = pAudioPropertyBag->Load(NULL, NULL)))
            {
                // add the filter to the graph
                hr = m_pGraph->AddFilter(pAudioCapture, TEXT("AudioCapture"));
            }
        }
    }

    // now that we have the audio driver added, set up the DMO wrapper
    // filter and configure it to use the WMV9 encoder.
    if(SUCCEEDED(hr))
    {
        // cocreate and add the DMO wrapper filter
        if(SUCCEEDED(hr = CoCreateInstance( CLSID_DMOWrapperFilter, NULL, 
                            CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID *)&pVideoDMOWrapperFilter )))
        {

            if(SUCCEEDED(hr = pVideoDMOWrapperFilter->QueryInterface( IID_IDMOWrapperFilter, (void **)&pVideoEncoderDMO )))
            {
                // now set the encoder wrapper filter to use the WMV9 encoder and add it to the graph
                if(SUCCEEDED(hr = pVideoEncoderDMO->Init( CLSID_CWMV9EncMediaObject, DMOCATEGORY_VIDEO_ENCODER )))
                    hr = m_pGraph->AddFilter(pVideoDMOWrapperFilter, TEXT("WMV9 DMO Encoder"));
            }
        }
    }


    // If the DMO succeeded and the auiod capture filter is created, set up the multiplexer
    // file writer, and render the stream.
    if(SUCCEEDED(hr))
    {
        // SetOutputFileName loads the multiplexer, retrieves the file sink interface, and
        // sets the initial file name. If the multiplexer has a separate file sink filter, 
        // it automatically determines the appropriate file sink and connects the multiplxer to it.
        if(SUCCEEDED(hr = m_pCaptureGraphBuilder->SetOutputFileName( &MEDIASUBTYPE_Asf, 
                            L"\\release\\MotionDetect.asf", &pMuxFilter, &pVideoFileSink )))
        {
            // now that we have everything created and ready to go, render the video capture filter
            // into the video encoder and then into the multiplexer. The buffering filter will be inserted
            // automatically based on media format negotiation.
            if(SUCCEEDED(hr = m_pCaptureGraphBuilder->RenderStream( &PIN_CATEGORY_CAPTURE, 
                                &MEDIATYPE_Video, m_pVideoCapture, pVideoDMOWrapperFilter, pMuxFilter)))
            {
                // now we render the audio capture filter into the multiplexer, if available an audio encoder
                // would be inserted between the audio capture filter and multiplexer. The buffering filter
                // will be inserted automatically based on media format negotiation.
                if(SUCCEEDED(hr = m_pCaptureGraphBuilder->RenderStream(NULL, &MEDIATYPE_Audio, 
                                    pAudioCapture, NULL, pMuxFilter)))
                {
                    // set the initial state of the capture pins to stopped
                    hr = m_pCaptureGraphBuilder->ControlStream( &PIN_CATEGORY_CAPTURE, NULL, NULL, 0, 0, 0, 0 );
                }
            }
        }
    }

    // release the created filters now that we're completely finished with them.
    if(pVideoDMOWrapperFilter)
    {
        pVideoDMOWrapperFilter->Release();
        pVideoDMOWrapperFilter = NULL;
    }
    if(pVideoEncoderDMO)
    {
        pVideoEncoderDMO->Release();
        pVideoEncoderDMO = NULL;
    }
    if(pAudioPropertyBag)
    {
        pAudioPropertyBag->Release();
        pAudioPropertyBag = NULL;
    }
    if(pAudioCapture)
    {
        pAudioCapture->Release();
        pAudioCapture = NULL;
    }
    if(pMuxFilter)
    {
        pMuxFilter->Release();
        pMuxFilter = NULL;
    }
    if(pVideoFileSink)
    {
        pVideoFileSink->Release();
        pVideoFileSink = NULL;
    }

    return hr;
}

HRESULT
CVideoCaptureGraph::Reset()
{
    HRESULT hr = S_OK;
    
    if(!m_fInitialized)
        return E_FAIL;
    
    // stop the graph
    if(SUCCEEDED(hr = m_pMediaControl->Stop()))
    {
        // run the graph
        hr = m_pMediaControl->Run();
    }

    return hr;
}

HRESULT
CVideoCaptureGraph::GetEvent(long* lEventCode, long* lParam1, long* lParam2, long msTimeout)
{
    if(!m_fInitialized || !m_pMediaEvent)
        return E_FAIL;

    return m_pMediaEvent->GetEvent( lEventCode, lParam1, lParam2, msTimeout );
}

HRESULT
CVideoCaptureGraph::FreeEvent(long lEventCode, long lParam1, long lParam2)
{
    return m_pMediaEvent->FreeEventParams( lEventCode, lParam1, lParam2 );
}

HRESULT
CVideoCaptureGraph::StartCapture()
{
    HRESULT hr = S_OK;
    REFERENCE_TIME rtStart = 0;
    REFERENCE_TIME rtStop = MAX_TIME;
    WORD StartCookie = 1, StopCookie = 2;

    if(!m_fInitialized)
        return E_FAIL;

    // start the capture. Because no type or filter is selected
    // the capture graph builder will iterate across all filters in the graph
    // which support IAMStreamControl.
    hr = m_pCaptureGraphBuilder->ControlStream( &PIN_CATEGORY_CAPTURE, 
                        NULL, NULL, &rtStart, &rtStop, StartCookie, StopCookie );

    return hr;
}

HRESULT
CVideoCaptureGraph::StopCapture()
{
    HRESULT hr = S_OK;
    REFERENCE_TIME rtStart = 0, rtStop = 0;
    WORD StartCookie = 3, StopCookie = 4;
    CComPtr<IMediaSeeking> pMediaSeeking;

    if(!m_fInitialized)
        return E_FAIL;

    hr = m_pGraph->QueryInterface( &pMediaSeeking );
    if(SUCCEEDED(hr) && pMediaSeeking)
    {
        hr = pMediaSeeking->GetCurrentPosition(&rtStop);
        if(SUCCEEDED(hr))
        {
            hr = m_pCaptureGraphBuilder->ControlStream( 
                &PIN_CATEGORY_CAPTURE, 
                NULL, NULL, 
                &rtStart, &rtStop, 
                StartCookie, StopCookie);
        }
    }

    return hr;
}


